#include "dcmtk/dcmdata/dcdeftag.h"
#include "dcmtk/dcmdata/dcsequen.h"
#include "dcmtk/dcmpstat/dvpspr.h"
#include "opencv2/freetype.hpp"

#include "util.h"

#include "print.h"


#define MY_AETITLE                        "ANY"

DCMPrint::DCMPrint(
    const std::string& aFont
)
{
    myFont = aFont;
    myMessageHandler = std::make_shared<DVPSPrintMessageHandler>();
}

DCMPrint::~DCMPrint()
{

}

bool
DCMPrint::InitNetwork(
    const ServerInfo& aServer
)
{
    OFCondition cond;

    OFStandard::initializeNetwork();

    //associate network
    cond = myMessageHandler->negotiateAssociation(
        nullptr,
        MY_AETITLE,
        aServer.myTitle.c_str(),
        aServer.myHost.c_str(),
        atoi(aServer.myPort.c_str()),
        ASC_DEFAULTMAXPDU,
        true,
        true,
        false
    );
    if (cond != EC_Normal) {
        std::cout << "associate network failed" << std::endl;
        return false;
    }

    return true;
}

bool
DCMPrint::GetPrinterInfo(
    PrinterModule& aPrinter
)
{
    OFCondition cond;

    Uint16 status = 0;
    DcmDataset* attributeListOut = nullptr;
    cond = myMessageHandler->getRQ(
        UID_PrinterSOPClass,
        UID_PrinterSOPInstance,
        nullptr,
        0,
        status,
        attributeListOut
    );

    do {
        if (cond != EC_Normal) {
            std::cout << "getRQ() failed" << std::endl;
            break;
        }

        if (attributeListOut) {
            cond = attributeListOut->findAndGetOFString(DCM_PrinterStatus, aPrinter.myPrinterStatus);
            if (cond != EC_Normal) {
                std::cout << "get PrinterStatus failed" << std::endl;
                break;
            }
            cond = attributeListOut->findAndGetOFString(DCM_PrinterName, aPrinter.myPrinterName);
            if (cond != EC_Normal) {
                std::cout << "get PrinterName failed" << std::endl;
                break;
            }
            cond = attributeListOut->findAndGetOFString(DCM_DateOfLastCalibration, aPrinter.myDateOfLastCalibration);
            if (cond != EC_Normal) {
                std::cout << "get DateOfLastCalibration failed" << std::endl;
                break;
            }
            cond = attributeListOut->findAndGetOFString(DCM_DeviceSerialNumber, aPrinter.myDeviceSerialNumber);
            if (cond != EC_Normal) {
                std::cout << "get DeviceSerialNumber failed" << std::endl;
                break;
            }
            cond = attributeListOut->findAndGetOFString(DCM_Manufacturer, aPrinter.myManufacturer);
            if (cond != EC_Normal) {
                std::cout << "get Manufacturer failed" << std::endl;
                break;
            }
            cond = attributeListOut->findAndGetOFString(DCM_ManufacturerModelName, aPrinter.myManufacturerModelName);
            if (cond != EC_Normal) {
                std::cout << "get ManufacturerModelName failed" << std::endl;
                break;
            }
            cond = attributeListOut->findAndGetOFString(DCM_PrinterStatusInfo, aPrinter.myPrinterStatusInfo);
            if (cond != EC_Normal) {
                std::cout << "get PrinterStatusInfo failed" << std::endl;
                break;
            }
            cond = attributeListOut->findAndGetOFString(DCM_SoftwareVersions, aPrinter.mySoftwareVersions);
            if (cond != EC_Normal) {
                std::cout << "get SoftwareVersions failed" << std::endl;
                break;
            }
            cond = attributeListOut->findAndGetOFString(DCM_TimeOfLastCalibration, aPrinter.myTimeOfLastCalibration);
            if (cond != EC_Normal) {
                std::cout << "get TimeOfLastCalibration failed" << std::endl;
                break;
            }
        }
    } while (false);

    delete attributeListOut;
    return EC_Normal == cond;
}

bool
DCMPrint::CreateFilmSessionBox(
    const FilmSessionBoxModule& aFilmSessionBox
)
{
    OFCondition cond;

    DcmDataset dataSet;
    if (dataSet.putAndInsertOFStringArray(DCM_MediumType, aFilmSessionBox.myMediuType) != EC_Normal) {
        std::cout << "insert MediumType failed" << std::endl;
        return false;
    }
    if (dataSet.putAndInsertOFStringArray(DCM_FilmDestination, aFilmSessionBox.myFilmDestination) != EC_Normal) {
        std::cout << "insert FilmDestination failed" << std::endl;
        return false;
    }
    if (dataSet.putAndInsertOFStringArray(DCM_NumberOfCopies, aFilmSessionBox.myNumberofCopies) != EC_Normal) {
        std::cout << "insert NumberOfCopies failed" << std::endl;
        return false;
    }
    if (dataSet.putAndInsertOFStringArray(DCM_PrintPriority, aFilmSessionBox.myFilmPriority) != EC_Normal) {
        std::cout << "insert PrintPriority failed" << std::endl;
        return false;
    }

    DcmDataset* attributeListOut = nullptr;
    Uint16 status = 0;
    myFilmSessionBoxUid = Util::GenerateUid();
    cond = myMessageHandler->createRQ(
        UID_BasicFilmSessionSOPClass,
        myFilmSessionBoxUid,
        &dataSet,
        status,
        attributeListOut
    );

    delete attributeListOut;
    if (cond != EC_Normal) {
        std::cout << "createRQ() failed" << std::endl;
        return false;
    }

    return true;
}

bool
DCMPrint::CreateFilmBox(
    const FilmBoxModule& aFilmBox,
    bool                 aSupportAnnotation,
    bool                 aSupportPresentationLUT
)
{
    OFCondition cond, errorCond;

    DcmDataset dataSet;
    if (dataSet.putAndInsertOFStringArray(DCM_ImageDisplayFormat, aFilmBox.myImageDisplayFormat) != EC_Normal) {
        std::cout << "insert ImageDisplayFormat failed" << std::endl;
        return false;
    }
    if (dataSet.putAndInsertOFStringArray(DCM_FilmSizeID, aFilmBox.myFilmSizeID) != EC_Normal) {
        std::cout << "insert FilmSizeID failed" << std::endl;
        return false;
    }
    if (dataSet.putAndInsertOFStringArray(DCM_FilmOrientation, aFilmBox.myFilmOrientation) != EC_Normal) {
        std::cout << "insert FilmOrientation failed" << std::endl;
        return false;
    }
    if (dataSet.putAndInsertOFStringArray(DCM_MagnificationType, aFilmBox.myMagnificationType) != EC_Normal) {
        std::cout << "insert MagnificationType failed" << std::endl;
        return false;
    }
    if (dataSet.putAndInsertOFStringArray(DCM_RequestedResolutionID, aFilmBox.myRequestedResolutionID) != EC_Normal) {
        std::cout << "insert RequestedResolutionID failed" << std::endl;
        return false;
    }
    if (dataSet.putAndInsertUint16(DCM_MaxDensity, aFilmBox.myMaxDensity) != EC_Normal) {
        std::cout << "insert MaxDensity failed" << std::endl;
        return false;
    }
    if (dataSet.putAndInsertUint16(DCM_MinDensity, aFilmBox.myMinDensity) != EC_Normal) {
        std::cout << "insert MinDensity failed" << std::endl;
        return false;
    }
    if (dataSet.putAndInsertOFStringArray(DCM_AnnotationDisplayFormatID, aFilmBox.myAnnotationDisplayFormatID) != EC_Normal) {
        std::cout << "insert AnnotationDisplayFormatID failed" << std::endl;
        return false;
    }

    //Referenced Film Session Sequence
    DcmSequenceOfItems* sequence = new DcmSequenceOfItems(DCM_ReferencedFilmSessionSequence);
    DcmItem* item = new DcmItem();
    if (sequence && item) {
        cond = item->putAndInsertString(DCM_ReferencedSOPClassUID, UID_BasicFilmSessionSOPClass);
        if (cond != EC_Normal) {
            std::cout << "insert ReferencedSOPClassUID failed" << std::endl;
            errorCond = cond;
        }
        cond = item->putAndInsertOFStringArray(DCM_ReferencedSOPInstanceUID, myFilmSessionBoxUid);
        if (cond != EC_Normal) {
            std::cout << "insert ReferencedSOPInstanceUID failed" << std::endl;
            errorCond = cond;
        }
        cond = sequence->insert(item);
        if (cond != EC_Normal) {
            std::cout << "ReferencedFilmSessionSequence insert item failed" << std::endl;
            errorCond = cond;
            delete item;
        }
        cond = dataSet.insert(sequence);
        if (cond != EC_Normal) {
            std::cout << "ReferencedFilmSessionSequence insert sequence failed" << std::endl;
            errorCond = cond;
            delete sequence;
        }
    } else{
        std::cout << "sequence or item is nullptr" << std::endl;
        delete sequence;
        delete item;
        return false;
    }

    //Referenced Presentation LUT Sequence
    if (aSupportPresentationLUT && myMessageHandler->printerSupportsPresentationLUT()) {
        if (dataSet.putAndInsertUint16(DCM_Illumination, aFilmBox.myIllumination) != EC_Normal) {
            std::cout << "insert Illumination failed" << std::endl;
            return false;
        }
        if (dataSet.putAndInsertUint16(DCM_ReflectedAmbientLight, aFilmBox.myReflectedAmbientLight) != EC_Normal) {
            std::cout << "insert ReflectedAmbientLight failed" << std::endl;
            return false;
        }

        DcmSequenceOfItems* sequence = new DcmSequenceOfItems(DCM_ReferencedPresentationLUTSequence);
        DcmItem* item = new DcmItem();
        if (sequence && item) {
            if (item->putAndInsertString(DCM_ReferencedSOPClassUID, UID_PresentationLUTSOPClass) != EC_Normal) {
                std::cout << "insert ReferencedSOPClassUID failed" << std::endl;
            }
            if (item->putAndInsertOFStringArray(DCM_ReferencedSOPInstanceUID, myPresentationLUTUid) != EC_Normal) {
                std::cout << "insert ReferencedSOPInstanceUID failed" << std::endl;
            }
            cond = sequence->insert(item);
            if (cond != EC_Normal) {
                std::cout << "ReferencedPresentationLUTSequence insert item failed" << std::endl;
                errorCond = cond;
                delete item;
            }
            cond = dataSet.insert(sequence);
            if (cond != EC_Normal) {
                std::cout << "ReferencedPresentationLUTSequence insert sequence failed" << std::endl;
                errorCond = cond;
                delete sequence;
            }
        } else {
            std::cout << "sequence or item is nullptr" << std::endl;
            delete sequence;
            delete item;
            return false;
        }
    }

    Uint16 status = 0;
    DcmDataset* attributeListOut = nullptr;
    myFilmBoxUid[aFilmBox.myFilmBoxNumber].push_back(Util::GenerateUid());
    cond = myMessageHandler->createRQ(
        UID_BasicFilmBoxSOPClass,
        myFilmBoxUid[aFilmBox.myFilmBoxNumber][0],
        &dataSet,
        status,
        attributeListOut
    );
    if (cond != EC_Normal) {
        std::cout << "createRQ() failed" << std::endl;
        errorCond = cond;
    }

    //Referenced Image Box Sequence
    DcmStack imageStack;
    unsigned long imageItemNum = 0;
    std::string imageSopClassUid;
    std::string imageSopInstanceUid;
    cond = attributeListOut->search(DCM_ReferencedImageBoxSequence, imageStack, ESM_fromHere, OFFalse);
    if (cond != EC_Normal) {
        std::cout << "ReferencedImageBoxSequence search() failed" << std::endl;
        errorCond = cond;
    }
    DcmSequenceOfItems* imageSequence = static_cast<DcmSequenceOfItems*>(imageStack.top());
    if (imageSequence) {
        imageItemNum = imageSequence->card();
        for (unsigned long i = 0; i < imageItemNum; ++i)
        {
            DcmItem* imageItem = imageSequence->getItem(i);
            if (imageItem) {
                cond = imageItem->findAndGetOFString(DCM_ReferencedSOPClassUID, imageSopClassUid);
                if (cond != EC_Normal) {
                    std::cout << "find ReferencedSOPClassUID failed" << std::endl;
                    errorCond = cond;
                }
                cond = imageItem->findAndGetOFString(DCM_ReferencedSOPInstanceUID, imageSopInstanceUid);
                if (cond != EC_Normal) {
                    std::cout << "find ReferencedSOPInstanceUID failed" << std::endl;
                    errorCond = cond;
                }
                myFilmBoxUid[aFilmBox.myFilmBoxNumber].push_back(imageSopInstanceUid);
            }
        }
    }

    //Referenced Basic Annotation Box Sequence
    if (aSupportAnnotation && myMessageHandler->printerSupportsAnnotationBox()) {
        DcmStack annoStack;
        unsigned long annoItemNum = 0;
        std::string annoSopClassUid;
        std::string annoSopInstanceUid;
        cond = attributeListOut->search(DCM_ReferencedBasicAnnotationBoxSequence, annoStack, ESM_fromHere, OFFalse);
        if (cond != EC_Normal) {
            std::cout << "ReferencedBasicAnnotationBoxSequence search() failed" << std::endl;
            errorCond = cond;
        }
        DcmSequenceOfItems* annoSequence = static_cast<DcmSequenceOfItems*>(annoStack.top());
        annoItemNum = annoSequence->card();
        if (annoSequence) {
            for (unsigned long i = 0; i < annoItemNum; ++i)
            {
                DcmItem* annoItem = annoSequence->getItem(i);
                if (annoItem) {
                    cond = annoItem->findAndGetOFString(DCM_ReferencedSOPClassUID, annoSopClassUid);
                    if (cond != EC_Normal) {
                        std::cout << "find ReferencedSOPClassUID failed" << std::endl;
                        errorCond = cond;
                    }
                    cond = annoItem->findAndGetOFString(DCM_ReferencedSOPInstanceUID, annoSopInstanceUid);
                    if (cond != EC_Normal) {
                        std::cout << "find ReferencedSOPInstanceUID failed" << std::endl;
                        errorCond = cond;
                    }
                }
                myAnnotationBoxUid.push_back(annoSopInstanceUid);
            }
        }
    }

    delete attributeListOut;
    return EC_Normal == errorCond;
}

bool
DCMPrint::SetImageBox(
    const ImageBoxModule&                             aImageBox,
    int                                               aFilmBoxNumber
)
{
    OFCondition cond, errorCond;

    DcmDataset dataSet;
    cond = dataSet.putAndInsertUint16(DCM_ImageBoxPosition, aImageBox.myImageBoxPosition + 1);
    if (cond != EC_Normal) {
        std::cout << "insert ImageBoxPosition failed" << std::endl;
        return false;
    }

    DcmSequenceOfItems* sequence = new DcmSequenceOfItems(DCM_BasicGrayscaleImageSequence);
    DcmItem* item = new DcmItem();
    if (sequence && item) {
        cond = item->putAndInsertUint16(DCM_SamplesPerPixel, aImageBox.mySamplesPerPixel);
        if (cond != EC_Normal) {
            std::cout << "insert SamplesPerPixel failed" << std::endl;
            errorCond = cond;
        }
        cond = item->putAndInsertOFStringArray(DCM_PhotometricInterpretation, aImageBox.myPhotometricInterpretation);
        if (cond != EC_Normal) {
            std::cout << "insert PhotometricInterpretation failed" << std::endl;
            errorCond = cond;
        }
        cond = item->putAndInsertUint16(DCM_PixelRepresentation, aImageBox.myPixelRepresentation);
        if (cond != EC_Normal) {
            std::cout << "insert PixelRepresentation failed" << std::endl;
            errorCond = cond;
        }
        cond = item->putAndInsertUint16(DCM_Rows, aImageBox.myRows);
        if (cond != EC_Normal) {
            std::cout << "insert Rows failed" << std::endl;
            errorCond = cond;
        }
        cond = item->putAndInsertUint16(DCM_Columns, aImageBox.myColumns);
        if (cond != EC_Normal) {
            std::cout << "insert Columns failed" << std::endl;
            errorCond = cond;
        }
        cond = item->putAndInsertOFStringArray(DCM_PixelAspectRatio, aImageBox.myPixelAspectRatio);
        if (cond != EC_Normal) {
            std::cout << "insert PixelAspectRatio failed" << std::endl;
            errorCond = cond;
        }
        cond = item->putAndInsertUint16(DCM_BitsAllocated, aImageBox.myBitsAllocated);
        if (cond != EC_Normal) {
            std::cout << "insert BitsAllocated failed" << std::endl;
            errorCond = cond;
        }
        if (16 == aImageBox.myBitsAllocated) {
            if (item->putAndInsertUint16Array(DCM_PixelData, reinterpret_cast<Uint16*>(aImageBox.myPixelData), aImageBox.myRows * aImageBox.myColumns * aImageBox.mySamplesPerPixel) != EC_Normal) {
                std::cout << "insert PixelData failed" << std::endl;
                errorCond = cond;
            }
            cond = item->putAndInsertUint16(DCM_BitsStored, 12);
            if (cond != EC_Normal) {
                std::cout << "insert BitsStored failed" << std::endl;
                errorCond = cond;
            }
            cond = item->putAndInsertUint16(DCM_HighBit, 11);
            if (cond != EC_Normal) {
                std::cout << "insert HighBit failed" << std::endl;
                errorCond = cond;
            }
        } else if (8 == aImageBox.myBitsAllocated) {
            if (item->putAndInsertUint8Array(DCM_PixelData, reinterpret_cast<Uint8*>(aImageBox.myPixelData), aImageBox.myRows * aImageBox.myColumns * aImageBox.mySamplesPerPixel) != EC_Normal) {
                std::cout << "insert PixelData failed" << std::endl;
                errorCond = cond;
            }
            cond = item->putAndInsertUint16(DCM_BitsStored, 8);
            if (cond != EC_Normal) {
                std::cout << "insert BitsStored failed" << std::endl;
                errorCond = cond;
            }
            cond = item->putAndInsertUint16(DCM_HighBit, 7);
            if (cond != EC_Normal) {
                std::cout << "insert HighBit failed" << std::endl;
                errorCond = cond;
            }
        }
        cond = sequence->insert(item);
        if (cond != EC_Normal) {
            std::cout << "BasicGrayscaleImageSequence insert() item failed" << std::endl;
            errorCond = cond;
            delete item;
        }
        cond = dataSet.insert(sequence);
        if (cond != EC_Normal) {
            std::cout << "BasicGrayscaleImageSequence insert() sequence failed" << std::endl;
            errorCond = cond;
            delete sequence;
        }
    } else {
        std::cout << "sequence or item is nullptr" << std::endl;
        delete sequence;
        delete item;
        return false;
    }

    Uint16 status = 0;
    DcmDataset* attributeListOut = nullptr;
    cond = myMessageHandler->setRQ(
        UID_BasicGrayscaleImageBoxSOPClass,
        myFilmBoxUid[aFilmBoxNumber][aImageBox.myImageBoxPosition + 1].c_str(),
        &dataSet,
        status,
        attributeListOut
    );
    if (cond != EC_Normal) {
        std::cout << "setRQ() failed" << std::endl;
        errorCond = cond;
    }

    delete attributeListOut;
    return EC_Normal == errorCond;
}

bool
DCMPrint::SetAnnotationBox(
    const AnnotationBoxModule& aAnnotationBox
)
{
    OFCondition cond;

    DcmDataset dataSet;
    cond = dataSet.putAndInsertUint16(DCM_AnnotationPosition, aAnnotationBox.myAnnotationPosition);
    if (cond != EC_Normal) {
        std::cout << "insert AnnotationPosition failed" << std::endl;
        return false;
    }
    cond = dataSet.putAndInsertString(DCM_TextString, aAnnotationBox.myTextString.c_str());
    if (cond != EC_Normal) {
        std::cout << "insert TextString failed" << std::endl;
        return false;
    }

    DcmDataset* attributeListOut = nullptr;
    Uint16 status = 0;
    std::string uid = Util::GenerateUid();
    cond = myMessageHandler->setRQ(
        UID_BasicAnnotationBoxSOPClass,
        uid.c_str(),
        &dataSet,
        status,
        attributeListOut
    );
    if (cond != EC_Normal) {
        std::cout << "setRQ() failed" << std::endl;
    }

    delete attributeListOut;
    return EC_Normal == cond;
}

bool
DCMPrint::PrintFilmSessionBox(
)
{
    OFCondition cond;

    DcmDataset* attributeListOut = nullptr;
    Uint16 status = 0;
    cond = myMessageHandler->actionRQ(
        UID_BasicFilmSessionSOPClass,
        myFilmSessionBoxUid.c_str(),
        1, //action type ID 1 = DCMPrint
        nullptr, //no action information
        status,
        attributeListOut
    );
    if (cond != EC_Normal) {
        std::cout << "actionRQ() failed" << std::endl;
    }

    delete attributeListOut;
    return EC_Normal == cond;
}

bool
DCMPrint::PrintFilmBox(
    int                    aFilmBoxNumber
)
{
    OFCondition cond;

    DcmDataset* attributeListOut = nullptr;
    Uint16 status = 0;

    cond = myMessageHandler->actionRQ(
        UID_BasicFilmBoxSOPClass,
        myFilmBoxUid[aFilmBoxNumber][0].c_str(),
        1,
        nullptr,
        status,
        attributeListOut
    );
    if (cond != EC_Normal) {
        std::cout << "actionRQ() failed" << std::endl;
    }

    delete attributeListOut;
    return EC_Normal == cond;
}

bool
DCMPrint::DeleteAllBox(
)
{
    OFCondition cond, errorCond;

    Uint16 status = 0;

    //delete basic film box
    for (auto it = myFilmBoxUid.begin(); it != myFilmBoxUid.end(); ++it) {
        cond = myMessageHandler->deleteRQ(
            UID_BasicFilmBoxSOPClass,
            it->second[0].c_str(),
            status
        );
        if (cond != EC_Normal) {
            std::cout << "deleteRQ() failed" << std::endl;
            errorCond = cond;
        }
    }
    myFilmBoxUid.clear();

    //delete basic film session
    cond = myMessageHandler->deleteRQ(
        UID_BasicFilmSessionSOPClass,
        myFilmSessionBoxUid.c_str(),
        status
    );
    if (cond != EC_Normal) {
        std::cout << "deleteRQ() failed" << std::endl;
        errorCond = cond;
    }
    myFilmSessionBoxUid.clear();

    //delete lut
    if (!myPresentationLUTUid.empty()) {
        cond = myMessageHandler->deleteRQ(
            UID_PresentationLUTSOPClass,
            myPresentationLUTUid.c_str(),
            status
        );
        if (cond != EC_Normal) {
            std::cout << "deleteRQ() failed" << std::endl;
            errorCond = cond;
        }
        myPresentationLUTUid.clear();
    }

    return EC_Normal == cond;
}

bool
DCMPrint::ReleaseNetwork()
{
    if (myMessageHandler->releaseAssociation() != EC_Normal) {
        return false;
    }

    OFStandard::shutdownNetwork();

    return true;
}

bool
DCMPrint::CreatePresentationLUT(
    const PresentationLUTModule& aLut,
    unsigned long                aCount
)
{
    OFCondition cond, errorCond;

    DcmDataset dataSet;
    if (dataSet.putAndInsertOFStringArray(DCM_PresentationLUTShape, aLut.myPresentationLUTShape) != EC_Normal) {
        return false;
    }
    DcmSequenceOfItems* sequence = new DcmSequenceOfItems(DCM_PresentationLUTSequence);
    DcmItem* item = new DcmItem();
    if (sequence && item) {
        if (item->putAndInsertUint16Array(DCM_LUTDescriptor, aLut.myLUTDescriptor, 3) != EC_Normal) {
            std::cout << "insert LUTDescriptor failed" << std::endl;
        }
        if (item->putAndInsertUint16Array(DCM_LUTData, aLut.myLUTData, aCount) != EC_Normal) {
            std::cout << "insert LUTData failed" << std::endl;
        }
        cond = sequence->insert(item);
        if (cond != EC_Normal) {
            std::cout << "PresentationLUTSequence insert() item failed" << std::endl;
            errorCond = cond;
            delete item;
        }
        cond = dataSet.insert(sequence);
        if (cond != EC_Normal) {
            std::cout << "PresentationLUTSequence insert() sequence failed" << std::endl;
            errorCond = cond;
            delete sequence;
        }
    }

    Uint16 status = 0;
    DcmDataset* attributeListOut = nullptr;
    myPresentationLUTUid = Util::GenerateUid();
    cond = myMessageHandler->createRQ(
        UID_PresentationLUTSOPClass,
        myPresentationLUTUid,
        &dataSet,
        status,
        attributeListOut
    );
    if (cond != EC_Normal) {
        std::cout << "createRQ() failed" << std::endl;
        errorCond = cond;
    }

    delete attributeListOut;
    return EC_Normal == errorCond;
}

bool
DCMPrint::GetPrinterConfiguration(
    PrinterConfigurationModule& aPrinterConfiguration
)
{
    OFCondition cond;

    Uint16 status = 0;
    DcmDataset* attributeListOut = nullptr;
    cond = myMessageHandler->getRQ(
        UID_PrinterConfigurationRetrievalSOPClass,
        UID_PrinterConfigurationRetrievalSOPInstance,
        nullptr,
        0,
        status,
        attributeListOut
    );

    do {
        if (cond != EC_Normal) {
            std::cout << "getRQ() failed" << std::endl;
            break;
        }

        if (attributeListOut) {
            cond = attributeListOut->findAndGetOFString(DCM_SOPClassesSupported, aPrinterConfiguration.mySOPClassesSupported);
            if (cond != EC_Normal) {
                std::cout << "find SOPClassesSupported failed" << std::endl;
                break;
            }
            cond = attributeListOut->findAndGetOFString(DCM_MaximumMemoryAllocation, aPrinterConfiguration.myMaximumMemoryAllocation);
            if (cond != EC_Normal) {
                std::cout << "find MaximumMemoryAllocation failed" << std::endl;
                break;
            }
            cond = attributeListOut->findAndGetUint16(DCM_MemoryBitDepth, aPrinterConfiguration.myMemoryBitDepth);
            if (cond != EC_Normal) {
                std::cout << "find MemoryBitDepth failed" << std::endl;
                break;
            }
            cond = attributeListOut->findAndGetUint16(DCM_PrintingBitDepth, aPrinterConfiguration.myPrintingBitDepth);
            if (cond != EC_Normal) {
                std::cout << "find PrintingBitDepth failed" << std::endl;
                break;
            }
            cond = attributeListOut->findAndGetOFString(DCM_MediumType, aPrinterConfiguration.myMediumType);
            if (cond != EC_Normal) {
                std::cout << "find MediumType failed" << std::endl;
                break;
            }
            cond = attributeListOut->findAndGetOFString(DCM_FilmSizeID, aPrinterConfiguration.myFilmSizeID);
            if (cond != EC_Normal) {
                std::cout << "find FilmSizeID failed" << std::endl;
                break;
            }
            cond = attributeListOut->findAndGetUint16(DCM_MaxDensity, aPrinterConfiguration.myMaxDensity);
            if (cond != EC_Normal) {
                std::cout << "find MaxDensity failed" << std::endl;
                break;
            }
            cond = attributeListOut->findAndGetUint16(DCM_MinDensity, aPrinterConfiguration.myMinDensity);
            if (cond != EC_Normal) {
                std::cout << "find MinDensity failed" << std::endl;
                break;
            }
        }
    } while (false);

    delete attributeListOut;
    return EC_Normal == cond;
}

bool
DCMPrint::GetPrintJob(
    PrintJobModule& aPrintJob
)
{
    OFCondition cond;

    Uint16 status = 0;
    DcmDataset* attributeListOut = nullptr;
    std::string uid = Util::GenerateUid();
    cond = myMessageHandler->getRQ(
        UID_PrintJobSOPClass,
        uid.c_str(),
        nullptr,
        0,
        status,
        attributeListOut
    );

    do {
        if (cond != EC_Normal) {
            std::cout << "getRQ() failed" << std::endl;
            break;
        }

        if (attributeListOut) {
            cond = attributeListOut->findAndGetOFString(DCM_ExecutionStatus, aPrintJob.myExecutionStatus);
            if (cond != EC_Normal) {
                std::cout << "find ExecutionStatus failed" << std::endl;
                break;
            }
            cond = attributeListOut->findAndGetOFString(DCM_CreationDate, aPrintJob.myCreationDate);
            if (cond != EC_Normal) {
                std::cout << "find CreationDate failed" << std::endl;
                break;
            }
            cond = attributeListOut->findAndGetOFString(DCM_CreationTime, aPrintJob.myCreationTime);
            if (cond != EC_Normal) {
                std::cout << "find CreationTime failed" << std::endl;
                break;
            }
            cond = attributeListOut->findAndGetOFString(DCM_ExecutionStatusInfo, aPrintJob.myExecutionStatusInfo);
            if (cond != EC_Normal) {
                std::cout << "find ExecutionStatusInfo failed" << std::endl;
                break;
            }
            cond = attributeListOut->findAndGetOFString(DCM_Originator, aPrintJob.myOriginator);
            if (cond != EC_Normal) {
                std::cout << "find Originator failed" << std::endl;
                break;
            }
            cond = attributeListOut->findAndGetOFString(DCM_PrinterName, aPrintJob.myPrinterName);
            if (cond != EC_Normal) {
                std::cout << "find PrinterName failed" << std::endl;
                break;
            }
            cond = attributeListOut->findAndGetOFString(DCM_PrintPriority, aPrintJob.myPrintPriority);
            if (cond != EC_Normal) {
                std::cout << "find PrintPriority failed" << std::endl;
                break;
            }
        }
    } while (false);

    delete attributeListOut;
    return EC_Normal == cond;
}

template <typename T>
constexpr inline const T& bound(
    const T&                        aMinValue,
    const T&                        aValue,
    const T&                        aMaxValue
)
{
    return std::max(aMinValue, std::min(aMaxValue, aValue));
}

//all hardcode value are Experience points. when work with printer, most value do not need to be calculated manually
bool
DCMPrint::SplicingOpenCVImage(
    const HeaderInfo&         aHeader,
    const CVType              aImageType,
    const std::string&        aPath
)
{
    if (myOpencvImage.size() > aHeader.myWidthNum * aHeader.myHeightNum) {
        std::cout << "image number > widthNum * heightNum" << std::endl;
        return false;
    }

    int headerHeight = 100;
    int boxWidth = 2400;
    int boxHeight = 2400 + headerHeight;

    //create cv::imge
    int imageType = CV_8UC3;
    cv::Mat boxImage(boxHeight, boxWidth, imageType, cv::Scalar::all(0));

    //add image
    int imageWidth = boxWidth / aHeader.myWidthNum;
    int imageHeight = (boxHeight - headerHeight) / aHeader.myHeightNum;
    int x = 0;
    int y = headerHeight;
    for (CVImageInfo image : myOpencvImage) {
        cv::Mat resize;
        cv::resize(image.first, resize, cv::Size(imageWidth, imageHeight));
        if (!AddCornerInfo(resize, image.second, aHeader.myWidthNum)) {
            std::cout << "add corner info failed" << std::endl;
            return false;
        }
        cv::Mat roi = boxImage(cv::Rect(x, y, imageWidth, imageHeight));
        resize.copyTo(roi);

        x += imageWidth;
        if (x >= boxWidth) {
            x = 0;
            y += imageHeight;
        }
    }

    //add header info
    int fontHeight = 25;
    int thickness = -1;
    int baseline = 0;
    cv::Scalar fontColor(255, 255, 255);

    cv::Ptr<cv::freetype::FreeType2> ft2 = cv::freetype::createFreeType2();
    if (nullptr == ft2) {
        std::cout << "cv::Ptr<cv::freetype::FreeType2> is nullptr" << std::endl;
        return false;
    }
    ft2->loadFontData(myFont, 0);

    cv::Size PatientNameSize = ft2->getTextSize(aHeader.myPatientName, fontHeight, thickness, &baseline);
    cv::Size PatientIDSize = ft2->getTextSize(aHeader.myPatientID, fontHeight, thickness, &baseline);
    cv::Size PatientSexSize = ft2->getTextSize(aHeader.myPatientSex, fontHeight, thickness, &baseline);
    cv::Size PatientAgeSize = ft2->getTextSize(aHeader.myPatientAge, fontHeight, thickness, &baseline);
    cv::Size PageNumberSize = ft2->getTextSize(aHeader.myPageNumber, fontHeight, thickness, &baseline);
    int headerSpace = 50;
    int pointWidth = headerSpace;
    int pointHeight = 20;

    cv::Mat headerImage(headerHeight, boxWidth, imageType, cv::Scalar::all(85));
    ft2->putText(headerImage, aHeader.myPatientName, cv::Point(pointWidth, pointHeight), fontHeight, fontColor, thickness, cv::LINE_AA, false);
    pointWidth += PatientNameSize.width + headerSpace;
    ft2->putText(headerImage, aHeader.myPatientSex, cv::Point(pointWidth, pointHeight), fontHeight, fontColor, thickness, cv::LINE_AA, false);
    pointWidth += PatientSexSize.width + headerSpace;
    ft2->putText(headerImage, aHeader.myPatientAge, cv::Point(pointWidth, pointHeight), fontHeight, fontColor, thickness, cv::LINE_AA, false);
    pointWidth += PatientAgeSize.width + headerSpace;

    pointWidth = headerSpace;
    pointHeight = 60;
    ft2->putText(headerImage, aHeader.myPatientID, cv::Point(pointWidth, pointHeight), fontHeight, fontColor, thickness, cv::LINE_AA, false);
    pointWidth += PatientIDSize.width + headerSpace;
    ft2->putText(headerImage, aHeader.myPageNumber, cv::Point(pointWidth, pointHeight), fontHeight, fontColor, thickness, cv::LINE_AA, false);
    cv::Mat roi = boxImage(cv::Rect(0, 0, boxImage.cols, headerHeight));
    headerImage.copyTo(roi);

    //save pixel file
    std::ofstream of(aPath, std::ios::out | std::ios::binary);
    if (!of.is_open()) {
        std::cout << "wrong file path" << std::endl;
        return false;
    }
    if (CVType::cv_8UC3 == aImageType) {
        of.write((char*)boxImage.data, boxImage.total() * boxImage.elemSize());
    } else if (CVType::cv_16UC1 == aImageType) {
        cv::Mat gray8, gray16;
        cv::cvtColor(boxImage, gray8, cv::COLOR_RGB2GRAY);
        gray8.convertTo(gray16, CV_16UC1, 255);
        of.write((char*)gray16.data, gray16.total() * gray16.elemSize());
    }
    of.close();

    myOpencvImage.clear();

    return true;
}

//all hardcode value are Experience points. when work with printer, most value do not need to be calculated manually
bool
DCMPrint::CreateOpenCVImage(
    const ImageParameter& aImageParam,
    const CornerInfoV&    aCorner
)
{
    //create opencv image
    cv::Mat cvImage(aImageParam.myRows, aImageParam.myColumns, CV_8UC3, cv::Scalar::all(0));
    CVImageInfo cvImageInfo(cvImage, aCorner);
    if (nullptr == aImageParam.myPixelData) {
        myOpencvImage.push_back(cvImageInfo);
        return true;
    }

    int valueMin = aImageParam.myWindowsCenter - (0.5 * aImageParam.myWindowsWidth);
    int valueMax = aImageParam.myWindowsCenter + (0.5 * aImageParam.myWindowsWidth);
    unsigned short* pixArray = reinterpret_cast<unsigned short*>(aImageParam.myPixelData);

    int imagePos = 0;
    int dataPos = 0;
    for (int row = 0; row < aImageParam.myRows; ++row)
    {
        for (int column = 0; column < aImageParam.myColumns; ++column)
        {
            int rawValue = pixArray[imagePos++];
            float pixValue = (bound(valueMin, rawValue, valueMax) - valueMin) * UCHAR_MAX / aImageParam.myWindowsWidth;
            unsigned char visualPix = bound(0, static_cast<int>(pixValue), UCHAR_MAX);
            cvImage.data[dataPos++] = visualPix;
            cvImage.data[dataPos++] = visualPix;
            cvImage.data[dataPos++] = visualPix;
        }
    }

    //scale
    int scaleNum = 10;
    int scaleLength = 10 / aImageParam.myPixelSpacingY * 10;
    int scaleMargin = 14;
    int tickLength = 0;
    cv::Scalar color(255, 255, 255);

    line(cvImage, cv::Point(cvImage.cols - scaleMargin, (cvImage.cols - scaleLength) / 2), cv::Point(cvImage.cols - scaleMargin, (cvImage.cols + scaleLength) / 2), color, 1);

    for (int i = 0; i <= scaleNum; ++i) {
        if (0 == i % 5) {
            tickLength = scaleLength / 10;
        } else {
            tickLength = scaleLength / 10 / 2;
        }
        line(cvImage, cv::Point(cvImage.cols - scaleMargin, (cvImage.cols - scaleLength) / 2 + i * scaleLength / 10), cv::Point(cvImage.cols - scaleMargin - tickLength, (cvImage.cols - scaleLength) / 2 + i * scaleLength / 10), color, 1);
    }

    myOpencvImage.push_back(cvImageInfo);

    return true;
}

//all hardcode value are Experience points. when work with printer, most value do not need to be calculated manually
bool
DCMPrint::AddCornerInfo(
    const cv::Mat& aMat,
    const CornerInfoV& aCorner,
    int aTime
)
{
    //add corner info
    int fontHeight = 40 - 5 * aTime;
    int thickness = -1;
    int baseline = 0;
    int margin = 10 - aTime;
    int textHeight = 0;
    cv::Size textSize;
    int wordSpace = 10 - aTime;
    cv::Scalar color(255, 255, 255);
    cv::Ptr<cv::freetype::FreeType2> ft2 = cv::freetype::createFreeType2();
    if (nullptr == ft2) {
        std::cout << "cv::Ptr<cv::freetype::FreeType2> is nullptr" << std::endl;
        return false;
    }
    std::ifstream stream(myFont);
    if (!stream.is_open()) {
        std::cout << myFont + " is not exist" << std::endl;
        return false;
    }
    ft2->loadFontData(myFont, 0);

    //leftTop
    textHeight = margin;
    for (int i = 0; i < aCorner.myLeftTop.size(); ++i) {
        textSize = ft2->getTextSize(aCorner.myLeftTop[i], fontHeight, thickness, &baseline);
        ft2->putText(aMat, aCorner.myLeftTop[i], cv::Point(margin, textHeight), fontHeight, color, thickness, cv::LINE_AA, false);
        textHeight += textSize.height + wordSpace;
    }
    //rightTop
    textHeight = margin;
    int arrowLength = 35 - 4 * aTime;
    int arrowThick = 1;
    int arrowLine = 8;
    int arrowShift = 0;
    double arrowTipLength = 0.4;
    for (int i = 0; i < aCorner.myRightTop.size(); ++i) {
        if ("arrow_left" == aCorner.myRightTop[i]) {
            textHeight += wordSpace;
            cv::arrowedLine(aMat, cv::Point(aMat.cols - 2 * margin, textHeight), cv::Point(aMat.cols - 2 * margin - arrowLength, textHeight), color,
                arrowThick, arrowLine, arrowShift, arrowTipLength);
            textHeight += wordSpace;
            continue;
        } else if ("arrow_right" == aCorner.myRightTop[i]) {
            textHeight += wordSpace;
            cv::arrowedLine(aMat, cv::Point(aMat.cols - arrowLength - 2 * margin, textHeight), cv::Point(aMat.cols - 2 * margin, textHeight), color,
                arrowThick, arrowLine, arrowShift, arrowTipLength);
            textHeight += wordSpace;
            continue;
        } else if ("arrow_top" == aCorner.myRightTop[i]) {
            textHeight += wordSpace;
            cv::arrowedLine(aMat, cv::Point(aMat.cols - arrowLength / 2 - 2 * margin, textHeight + arrowLength), cv::Point(aMat.cols - arrowLength / 2 - 2 * margin, textHeight), color,
                arrowThick, arrowLine, arrowShift, arrowTipLength);
            textHeight += wordSpace + arrowLength;
            continue;
        } else if ("arrow_bottom" == aCorner.myRightTop[i]) {
            textHeight += wordSpace;
            cv::arrowedLine(aMat, cv::Point(aMat.cols - arrowLength / 2 - 2 * margin, textHeight), cv::Point(aMat.cols - arrowLength / 2 - 2 * margin, textHeight + arrowLength), color,
                arrowThick, arrowLine, arrowShift, arrowTipLength);
            textHeight += wordSpace + arrowLength;
            continue;
        }

        textSize = ft2->getTextSize(aCorner.myRightTop[i], fontHeight, thickness, &baseline);
        ft2->putText(aMat, aCorner.myRightTop[i], cv::Point(aMat.cols - textSize.width - margin, textHeight), fontHeight, color, thickness, cv::LINE_AA, false);
        textHeight += textSize.height + wordSpace;
    }
    //leftBottom
    textHeight = margin;
    for (int i = 0; i < aCorner.myLeftBottom.size(); ++i) {
        textSize = ft2->getTextSize(aCorner.myLeftBottom[i], fontHeight, thickness, &baseline);
        textHeight += textSize.height + wordSpace;
        ft2->putText(aMat, aCorner.myLeftBottom[i], cv::Point(margin, aMat.rows - textHeight), fontHeight, color, thickness, cv::LINE_AA, false);
    }
    //rightBottom
    textHeight = margin;
    for (int i = 0; i < aCorner.myRightBottom.size(); ++i) {
        textSize = ft2->getTextSize(aCorner.myRightBottom[i], fontHeight, thickness, &baseline);
        textHeight += textSize.height + wordSpace;
        ft2->putText(aMat, aCorner.myRightBottom[i], cv::Point(aMat.cols - textSize.width - margin, aMat.rows - textHeight), fontHeight, color, thickness, cv::LINE_AA, false);
    }
    //leftCenter
    textHeight = margin;
    for (int i = 0; i < aCorner.myLeftCenter.size(); ++i) {
        textSize = ft2->getTextSize(aCorner.myLeftCenter[i], fontHeight, thickness, &baseline);
        ft2->putText(aMat, aCorner.myLeftCenter[i], cv::Point(margin, (aMat.rows - textSize.height) / 2), fontHeight, color, thickness, cv::LINE_AA, false);
        textHeight += textSize.height + wordSpace;
    }
    //rigthCenter
    textHeight = margin;
    for (int i = 0; i < aCorner.myRightCenter.size(); ++i) {
        textSize = ft2->getTextSize(aCorner.myRightCenter[i], fontHeight, thickness, &baseline);
        ft2->putText(aMat, aCorner.myRightCenter[i], cv::Point(aMat.cols - textSize.width - margin, (aMat.rows - textSize.height) / 2), fontHeight, color, thickness, cv::LINE_AA, false);
        textHeight += textSize.height + wordSpace;
    }
    //topCenter
    textHeight = margin;
    for (int i = 0; i < aCorner.myTopCenter.size(); ++i) {
        textSize = ft2->getTextSize(aCorner.myTopCenter[i], fontHeight, thickness, &baseline);
        ft2->putText(aMat, aCorner.myTopCenter[i], cv::Point((aMat.cols - textSize.width) / 2, textHeight), fontHeight, color, thickness, cv::LINE_AA, false);
        textHeight += textSize.height + wordSpace;
    }
    //bottomCenter
    textHeight = margin;
    for (int i = 0; i < aCorner.myBottomCenter.size(); ++i) {
        textSize = ft2->getTextSize(aCorner.myBottomCenter[i], fontHeight, thickness, &baseline);
        ft2->putText(aMat, aCorner.myBottomCenter[i], cv::Point((aMat.cols - textSize.width) / 2, aMat.rows - textSize.height - 2 * margin), fontHeight, color, thickness, cv::LINE_AA, false);
        textHeight += textSize.height + wordSpace;
    }

    return true;
}
